use std::borrow::Cow;

use axum::{
    http::{header, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
};
use serde::Serialize;

use crate::error::AppErr;

use super::{
    model::{Cbor, Download, Void},
    msg::MsgItem,
};

#[derive(Debug, Serialize)]
pub struct ErrInfo {
    err_code: i32,
    err_msg: Cow<'static, str>,
}

pub const HEADER_RESP: &'static str = "resp";
pub const HEADER_RESP_OK: HeaderValue = HeaderValue::from_static("ok");
pub const HEADER_RESP_FAIL: HeaderValue = HeaderValue::from_static("fail");
pub const HEADER_SEQ: &'static str = "seq";
pub const HEADER_TY: &'static str = "ty";
pub const HEADER_CMD: &'static str = "cmd";

pub const CONTENT_TYPE_BINARY: HeaderValue = HeaderValue::from_static("application/octet-stream");

#[inline(always)]
fn create_resp<T: Serialize>(value: &T) -> Response {
    match serde_cbor::to_vec(value) {
        Ok(v) => v.into_response(),
        Err(e) => {
            log::error!("serial error:{}", e);
            "".into_response()
        }
    }
}

impl IntoResponse for MsgItem {
    fn into_response(self) -> Response {
        let mut resp = self.body.into_response();
        *resp.status_mut() = StatusCode::OK;
        resp.headers_mut().insert(HEADER_RESP, HEADER_RESP_OK);
        if let Ok(seq) = HeaderValue::from_str(&format!("{}", self.seq)) {
            resp.headers_mut().insert(HEADER_SEQ, seq);
        }
        if let Ok(ty) = HeaderValue::from_str(&format!("{}", self.ty)) {
            resp.headers_mut().insert(HEADER_TY, ty);
        }
        if let Ok(cmd) = HeaderValue::from_str(&self.cmd) {
            resp.headers_mut().insert(HEADER_CMD, cmd);
        }
        resp
    }
}

impl IntoResponse for AppErr {
    fn into_response(self) -> Response {
        let info = match self {
            Self::Service(ec, msg) => ErrInfo {
                err_code: ec,
                err_msg: msg,
            },

            _ => ErrInfo {
                err_code: -1,
                err_msg: Cow::Owned(self.to_string()),
            },
        };

        let mut resp = create_resp(&info);
        *resp.status_mut() = StatusCode::OK;
        resp.headers_mut().insert(HEADER_RESP, HEADER_RESP_FAIL);
        resp.headers_mut()
            .insert(header::CONTENT_TYPE, CONTENT_TYPE_BINARY);
        resp
    }
}

impl<T: Serialize> IntoResponse for Cbor<T> {
    fn into_response(self) -> Response {
        let mut resp = create_resp(&self.0);
        *resp.status_mut() = StatusCode::OK;
        resp.headers_mut().insert(HEADER_RESP, HEADER_RESP_OK);
        resp.headers_mut()
            .insert(header::CONTENT_TYPE, CONTENT_TYPE_BINARY);
        resp
    }
}

impl IntoResponse for Void {
    fn into_response(self) -> Response {
        let mut resp = StatusCode::OK.into_response();
        resp.headers_mut().insert(HEADER_RESP, HEADER_RESP_OK);
        resp
    }
}

const FILE_NAME: HeaderValue = HeaderValue::from_static(r#"attachment; filename="unknown""#);

impl IntoResponse for Download {
    fn into_response(self) -> Response {
        let mut resp = self.body.into_response();
        *resp.status_mut() = StatusCode::OK;
        resp.headers_mut().insert(HEADER_RESP, HEADER_RESP_OK);
        resp.headers_mut()
            .insert(header::CONTENT_TYPE, CONTENT_TYPE_BINARY);
        let file = format!(r#"attachment; filename="{}""#, self.name);
        resp.headers_mut().insert(
            "Content-Disposition",
            HeaderValue::from_str(&file).unwrap_or(FILE_NAME),
        );
        resp
    }
}
